﻿//=============================================================================
// SharpTemplate.NET - Generic template parser
//
// Copyright (c) 2008 Philippe Leybaert
//
// Permission is hereby granted, free of charge, to any person obtaining a copy 
// of this software and associated documentation files (the "Software"), to deal 
// in the Software without restriction, including without limitation the rights 
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is 
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//=============================================================================

using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Activa.LazyParser;

namespace Activa.SharpTemplate
{
	public class SharpTemplate<TConfig> : SharpTemplate<TConfig, CSharpParser> where  TConfig:SharpTemplateConfig,new()
	{
	}

	public class SharpTemplate<TConfig,TParser> : SharpTemplate where TParser:ExpressionParser,new() where  TConfig:SharpTemplateConfig,new()
	{
		public SharpTemplate() : base(new TConfig() , new TParser())
		{
		}
	}

	public class SharpTemplate
    {
		private readonly ExpressionParser _parser;
		private readonly SharpTemplateConfig _config;

		public SharpTemplate(SharpTemplateConfig config)
		{
			_config = config;
		}

		public SharpTemplate(SharpTemplateConfig config, ExpressionParser parser)
		{
			_config = config;
			_parser = parser;
		}

		public ExpressionParser Parser
        {
            get { return _parser; }
        }

    	public SharpTemplateConfig Config
    	{
    		get { return _config; }
    	}

    	public event EventHandler<EvaluatingIfEventArgs> EvaluatingIf;
		public event EventHandler<EvaluatingForeachEventArgs> EvaluatingForeach;
		public event EventHandler<EvaluatingExpressionEventArgs> EvaluatingExpression;
		public event EventHandler<EvaluatingTextEventArgs> EvaluatingText;
        public event EventHandler<EvaluatingIteratorEventArgs> EvaluatingIterator;

        public event EventHandler<EvaluatedIfEventArgs> EvaluatedIf;
		public event EventHandler<EvaluatedForeachEventArgs> EvaluatedForeach;
		public event EventHandler<EvaluatedExpressionEventArgs> EvaluatedExpression;
		public event EventHandler<EvaluatedTextEventArgs> EvaluatedText;
        public event EventHandler<EvaluatedIteratorEventArgs> EvaluatedIterator;

        public PreParsedTemplate ParseFile(string fileName)
        {
            return Parse(File.ReadAllText(fileName));
        }

    	public PreParsedTemplate Parse(string inputString)
        {
            Stack<Node> nodeStack = new Stack<Node>();

            Node currentNode = new Node();

            int lastIndex = 0;

            Regex regex = Config.Regex;

        	TextNode lastTextNode = null;
        	bool checkEmptyLine = false;

            foreach (Match match in regex.Matches(inputString))
            {
                int newIndex = match.Index;

				if (newIndex > lastIndex)
				{
					string text = inputString.Substring(lastIndex, newIndex - lastIndex);

					if (checkEmptyLine)
						text = CheckEmptyLine(lastTextNode, text);

					lastTextNode = (TextNode) currentNode.Add(new TextNode(text));

					checkEmptyLine = false;
				}

            	lastIndex = match.Index + match.Length;

                TokenMatch tokenMatch = Config.FindTokenMatch(regex, match);

            	checkEmptyLine = tokenMatch.RemoveEmptyLine && !checkEmptyLine;

                switch (tokenMatch.TokenType)
                {
                    case TemplateTokenType.Expression:
                        {
                            currentNode.Add(new ExpressionNode(tokenMatch));
                        }
                        break;

                    case TemplateTokenType.ForEach:
                        {
                            nodeStack.Push(currentNode);

                            currentNode = currentNode.Add(new ForEachNode(tokenMatch));
                        }
                        break;

                    case TemplateTokenType.If:
                        {
                            nodeStack.Push(currentNode);

                            IfNode ifNode = (IfNode)currentNode.Add(new IfNode(tokenMatch));

                            ifNode.TrueNode = new Node();

                            nodeStack.Push(ifNode);

                            currentNode = ifNode.TrueNode;
                        }
                        break;

                    case TemplateTokenType.ElseIf:
                        {
                            IfNode ifNode = (IfNode)nodeStack.Peek();

                            currentNode = ifNode.FalseNode = new Node();

                            ifNode = (IfNode)currentNode.Add(new IfNode(tokenMatch));

                            ifNode.TrueNode = new Node();

                            nodeStack.Push(ifNode);

                            currentNode = ifNode.TrueNode;
                        }
                        break;

                    case TemplateTokenType.Else:
                        {
                            IfNode ifNode = (IfNode)nodeStack.Peek();

                            currentNode = ifNode.FalseNode = new Node();
                        }
                        break;

                    case TemplateTokenType.EndBlock:
                        {
                            while (nodeStack.Peek() is IfNode)
                            {
                                nodeStack.Pop();
                            }

                            currentNode = nodeStack.Pop();
                        }
                        break;
                }

            }

			if (inputString.Length - 1 > lastIndex)
			{
				string text = inputString.Substring(lastIndex);

				if (checkEmptyLine)
					text = CheckEmptyLine(lastTextNode, text);
				
				currentNode.Add(new TextNode(text));
			}

//        	currentNode.DebugPrint(0);

            return new PreParsedTemplate(currentNode);
        }

    	private string CheckEmptyLine(TextNode lastTextNode, string text)
    	{
    		string prevText = lastTextNode == null ? "":lastTextNode.Text;

    		Match m1 = Regex.Match(prevText, @"\n[\x20\t]*$",RegexOptions.Singleline);

    		if (m1.Success)
    		{
    			Match m2 = Regex.Match(text, @"^[\x20\t\r]*?\n", RegexOptions.Singleline);

    			if (m2.Success)
    			{
    				if (lastTextNode != null)
    					lastTextNode.Text = lastTextNode.Text.Substring(0,m1.Index+1);

    				text = text.Substring(m2.Length);
    			}
    		}
    		return text;
    	}

        public string RenderFile(string fileName, IParserContext context)
        {
            return Render(ParseFile(fileName), context);
        }

    	public string Render(PreParsedTemplate parseTree, IParserContext context)
        {
    		context = context.CreateLocal();

    		context.AssignmentPermissions |= AssignmentPermissions.Variable;

            StringBuilder outputBuffer = new StringBuilder();

            BuildOutput(parseTree.Tree, outputBuffer, context);

            return outputBuffer.ToString();
        }

        public string Render(string inputString, IParserContext context)
        {
            return Render(Parse(inputString), context);
        }

        private void BuildOutput(Node rootNode, StringBuilder outputBuffer, IParserContext context)
        {
            if (rootNode == null)
                return;

            foreach (Node node in rootNode.Children)
            {
                if (node is ForEachNode)
                {
                    ForEachNode forEachNode = (ForEachNode)node;

                	IEnumerable list = EvalForeach(forEachNode.TokenMatch, context);

                    if (list != null)
                    {
                        IParserContext localContext = context.CreateLocal();

                        int rowNum = 1;

                        foreach (object listItem in list)
                        {
                            localContext.Set(forEachNode.Iterator, listItem, listItem == null ? typeof(object) : listItem.GetType());

							EvalIteration(forEachNode.Iterator, rowNum, listItem, localContext);

                            BuildOutput(forEachNode, outputBuffer, localContext);

                            rowNum++;
                        }
                    }
                }

                else if (node is IfNode)
                {
                    IfNode ifNode = (IfNode)node;

                	bool result = EvalIf(ifNode.TokenMatch, context);

                    BuildOutput(result ? ifNode.TrueNode : ifNode.FalseNode, outputBuffer, context);
                }

                else if (node is ExpressionNode)
                {
                    ExpressionNode exprNode = (ExpressionNode)node;

                	string value = EvalExpression(exprNode.TokenMatch, context);

                    if (value != null)
                        outputBuffer.Append(value);
                }

                else if (node is TextNode)
                {
                    outputBuffer.Append(EvalText(((TextNode)node).Text));
                }
            }

        }

		protected virtual string OnEvalInclude(TokenMatch tokenMatch, IParserContext context)
		{
			return "";
		}

		protected virtual string OnEvalRender(TokenMatch tokenMatch, IParserContext context)
		{
			return "";
		}

		private string EvalExpression(TokenMatch tokenMatch, IParserContext context)
		{
			bool runDefault = true;
			string returnValue = null;

			if (EvaluatingExpression != null)
			{
				EvaluatingExpressionEventArgs eventArgs = new EvaluatingExpressionEventArgs(context, tokenMatch);

				EvaluatingExpression(this, eventArgs);

				runDefault = eventArgs.RunDefaultHandler;

				returnValue = eventArgs.Result;
			}

			if (runDefault)
				returnValue = OnEvalExpression(tokenMatch, context);

			if (EvaluatedExpression != null)
			{
				EvaluatedExpressionEventArgs eventArgs = new EvaluatedExpressionEventArgs(context, tokenMatch, returnValue);

				EvaluatedExpression(this, eventArgs);

				returnValue = eventArgs.Result;
			}

			return returnValue;
		}

		protected virtual string OnEvalExpression(TokenMatch tokenMatch, IParserContext context)
		{
		    return Config.EvalExpression(Parser, tokenMatch, context);
		}

		private bool EvalIf(TokenMatch tokenMatch, IParserContext context)
		{
			bool runDefault = true;
			bool returnValue = false;

			if (EvaluatingIf != null)
			{
				EvaluatingIfEventArgs eventArgs = new EvaluatingIfEventArgs(context, tokenMatch);

				EvaluatingIf(this, eventArgs);

				runDefault = eventArgs.RunDefaultHandler;
				returnValue = eventArgs.Result;
			}

			if (runDefault)
				returnValue = OnEvalIf(tokenMatch, context);

			if (EvaluatedIf != null)
			{
				EvaluatedIfEventArgs eventArgs = new EvaluatedIfEventArgs(context, tokenMatch, returnValue);

				EvaluatedIf(this, eventArgs);

				returnValue = eventArgs.Result;
			}

			return returnValue;
		}

		protected virtual bool OnEvalIf(TokenMatch tokenMatch, IParserContext context)
		{
		    return Config.EvalIf(Parser, tokenMatch, context);
		}

		private IEnumerable EvalForeach(TokenMatch tokenMatch, IParserContext context)
		{
			IEnumerable returnValue = null;
			bool runDefault = true;

			if (EvaluatingForeach != null)
			{
				EvaluatingForeachEventArgs eventArgs = new EvaluatingForeachEventArgs(context, tokenMatch);

				EvaluatingForeach(this, eventArgs);

				runDefault = eventArgs.RunDefaultHandler;
				returnValue = eventArgs.Result;
			}

			if (runDefault)
				returnValue = OnEvalForeach(tokenMatch, context);

			if (EvaluatedForeach != null)
			{
				EvaluatedForeachEventArgs eventArgs = new EvaluatedForeachEventArgs(context, tokenMatch, returnValue);

				EvaluatedForeach(this, eventArgs);

				returnValue = eventArgs.Result;
			}

			return returnValue;
		}

		protected virtual IEnumerable OnEvalForeach(TokenMatch tokenMatch, IParserContext context)
		{
		    return Config.EvalForeach(Parser, tokenMatch, context);
		}

        private void EvalIteration(string iteratorName, int rowNum, object obj, IParserContext localContext)
        {
            bool runDefault = true;

            if (EvaluatingIterator != null)
            {
                EvaluatingIteratorEventArgs eventArgs = new EvaluatingIteratorEventArgs(iteratorName, obj, rowNum, localContext);

                EvaluatingIterator(this, eventArgs);

                runDefault = eventArgs.RunDefaultHandler;
            }

            if (runDefault)
                OnEvalIteration(iteratorName, rowNum, obj, localContext);

            if (EvaluatedIterator != null)
            {
                EvaluatedIteratorEventArgs eventArgs = new EvaluatedIteratorEventArgs(iteratorName, obj, rowNum, localContext);

                EvaluatedIterator(this, eventArgs);
            }
        }

		protected virtual void OnEvalIteration(string iteratorName, int rowNum, object obj, IParserContext localContext)
		{
		    Config.EvalIteration(iteratorName, rowNum, obj, localContext);
		}

		private string EvalText(string text)
		{
			string returnValue = text;
			bool runDefault = true;

			if (EvaluatingText != null)
			{
				EvaluatingTextEventArgs eventArgs = new EvaluatingTextEventArgs(text);

				EvaluatingText(this, eventArgs);

				returnValue = eventArgs.Result;

				runDefault = eventArgs.RunDefaultHandler;
			}

			if (runDefault)
				returnValue = OnEvalText(text);

			if (EvaluatedText != null)
			{
				EvaluatedTextEventArgs eventArgs = new EvaluatedTextEventArgs(text, returnValue);

				EvaluatedText(this, eventArgs);

				returnValue = eventArgs.Result;
			}

			return returnValue;
			
		}

		protected virtual string OnEvalText(string text)
		{
		    return Config.EvalText(text);
		}

    }
}
